#include <cpu.h>
#include <utils.h>
#include <dpic.h>

CPU_state cpu = {};
Vtop *dut;
const char *regs[] = {
  "$0", "ra", "sp", "gp", "tp", "t0", "t1", "t2",
  "s0", "s1", "a0", "a1", "a2", "a3", "a4", "a5",
  "a6", "a7", "s2", "s3", "s4", "s5", "s6", "s7",
  "s8", "s9", "s10", "s11", "t3", "t4", "t5", "t6"
};

extern void read_rf(unsigned long long *rf);
extern "C" void ebreak();

#ifdef CONFIG_WAVE_TRACE
void dump();
void init_wave_trace();
void close_wave_trace();
#endif
void watchpoint_scan();

static void update_cpu_state() {
  static unsigned long long int temp[32];
  if(dut != NULL) {
    read_rf(temp);
    for(int i = 0; i < 32; i++) {
      rf(i) = temp[i];
    }
    cpu.pc = dut->pc_debug;
  }
  else 
    printf("update cpu state failed: device pointer is null.\n");
}

void reg_display() {
  printf("%s\t%#15lx%20ld\n", "pc", cpu.pc,  cpu.pc);
  for (int i = 0; i < 32; i++) {
    printf("%s\t%#15lx%20ld\n", regs[i], rf(i), rf(i));
  }
}

extern void (*ref_difftest_regcpy)(void *dut, bool direction);

void diff_reg_display() {
  CPU_state ref_r;
  ref_difftest_regcpy(&ref_r, 0);
  printf("%s\t%#15lx%20ld\n", "pc", ref_r.pc,  ref_r.pc);
  for (int i = 0; i < 32; i++) {
    printf("%s\t%#15lx%20ld\n", regs[i], ref_r.rf[i], ref_r.rf[i]);
  }
}

uint64_t reg_str2val(const char *s, bool *success) {
  //skip '$'
  if(!strcmp(s,"pc"))
    return cpu.pc;
  for(int i = 0; i < 32; i++) 
    if(!strcmp(s, regs[i])) return rf(i);
  printf("Reg name mot match:%s\n",s);
  *success = false;
  return 0;
}

void ebreak() {
  npc_state.state = NPC_END;
  npc_state.npc_halt_ret = rf(10);
  npc_state.npc_halt_pc = dut->pc_debug;
}

void difftest_step(uint64_t pc);
void difftest_skip_ref();
void device_update();


static void execute_once() {
  for(int i = 0; i < 2; i++) {
    dut->clk ^= 1;
    dut->eval();
    IFDEF(CONFIG_WAVE_TRACE, dump());
    sim_time++;
  }
  update_cpu_state();
  // if(dut->rst == 1)
  //   difftest_skip_ref();
  // difftest_step(cpu.pc);
  device_update();
}

static void execute(uint64_t n) {
  for(; n > 0; n--){
    execute_once();
    watchpoint_scan();
    if(npc_state.state != NPC_RUNNING)
      break;
  }
}

void cpu_execute(uint64_t n) {
  switch (npc_state.state) {
    case NPC_END: 
    case NPC_ABORT:
      Log("Program execution has ended. To restart the program, exit and run again.");
      return;
    default: npc_state.state = NPC_RUNNING;
  }
 
  execute(n);

  switch (npc_state.state) {
    case NPC_RUNNING:
      npc_state.state = NPC_STOP;
      break;
    case NPC_END:
    case NPC_ABORT:
      Log("npc: %s at pc = 0x%lX",
          (npc_state.state == NPC_ABORT ? ANSI_FMT("ABORT", ANSI_FG_RED) :
           (npc_state.npc_halt_ret == 0 ? ANSI_FMT("HIT GOOD TRAP", ANSI_FG_GREEN) :
            ANSI_FMT("HIT BAD TRAP", ANSI_FG_RED))),
          npc_state.npc_halt_pc);
      Log("Program execution has ended. To restart the program, exit and run again.");
    default: 
      return;
  }
}

void reset_cpu() {
  dut->rst = 1;
  cpu_execute(CPU_RESET_TIME);
  dut->rst = 0;
}

void init_cpu() {
  dut = new Vtop;
  void* dpi_scope = svGetScopeFromName("TOP.top.rf");
  svSetScope(dpi_scope);
  IFDEF(CONFIG_WAVE_TRACE, init_wave_trace());
}

void close_cpu() {
  IFDEF(CONFIG_WAVE_TRACE, close_wave_trace());
  delete dut;
}